using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using System.Drawing.Imaging;
using EnergyNode = CAIR.Lib.EnergyMap.Node;

namespace CAIR.Lib
{
    public class ImageResize
    {
        private UnsafeBitmap _internalImage;
        private IEnergyFunction _energyFunction;
        EnergyMap optimalSeams;
        public IEnergyFunction EnergyFunction
        {
            get { return _energyFunction; }
            set { _energyFunction = value; }
        }
        public Bitmap InternalImage
        {
            get { return _internalImage.Bitmap; }
        }

        /// <summary>
        /// Uses e1 as EnergyFunction
        /// </summary>
        /// <param name="bitmap"></param>
        public ImageResize(Bitmap bitmap)
        {
            _internalImage = new UnsafeBitmap(bitmap);
            _energyFunction = EnergyFunctionFactory.GetEnergyFunction(EnergyFunctionTypes.e1);
        }

        public ImageResize(Bitmap bitmap, IEnergyFunction energyFunction)
        {
            _internalImage = new UnsafeBitmap(bitmap);
            _energyFunction = energyFunction;
        }

        /// <summary>
        /// Resizes an image
        /// </summary>
        /// <param name="aspectX">The ratio (> 0) in x direction</param>
        /// <param name="aspectY">The ratio (> 0) in y direction</param>
        public bool Resize(double aspectX, double aspectY)
        {
            int resultantHeight = (int)aspectY;// (int)(_internalImage.Height * aspectY);
            int resultantWidth = (int)aspectX;// (int)(_internalImage.Width * aspectX);

            // TODO: Take care of shrinking vs magnifying
            UnsafeBitmap temp;
            int c = Math.Abs(resultantWidth - _internalImage.Width);
            int[] SeamPath = ComputeSeams();
            EnergyNode node;
            int index;
            for (int i = 0; i < c; i++)
            {
                temp = new UnsafeBitmap(_internalImage.Width - 1, _internalImage.Height);
                CopyBitmapAndRemoveSeam(_internalImage, temp, SeamPath);
                _internalImage = temp;
                GetMinimumSeam(optimalSeams, out node, out index);
                SeamPath = optimalSeams.Extract(node, index);
            }

            return true;
        }
        /// <summary>
        /// Coppys from one bitmap to anther skipping 
        /// </summary>
        /// <param name="InternalImage"></param>
        /// <param name="temp"></param>
        /// <param name="SeamStart"></param>
        /// <param name="SeamPath"></param>
        private void CopyBitmapAndRemoveSeam(UnsafeBitmap internalImage, UnsafeBitmap temp, int[] SeamPath)
        {
            temp.LockBitmap();
            internalImage.LockBitmap();
            for (int i = 0; i < internalImage.Height; i++)
            {
                int colIndex = 0;
                for (int j = 0; j < internalImage.Width; j++)
                {
                    if (j != SeamPath[i])
                        temp.SetPixel(colIndex++, i, internalImage.GetPixel(j, i));
                }
            }
            internalImage.UnlockBitmap();
            temp.UnlockBitmap();
        }
        private int[] ComputeSeams()
        {
            EnergyNode[] Result = new EnergyNode[_internalImage.Height];
            optimalSeams = new EnergyMap(_internalImage.Height, _internalImage.Width);
            Direction[,] directions = new Direction[_internalImage.Height, _internalImage.Width];

            EnergyNode node = optimalSeams.FirstNode;
            for (int i = 0; i < _internalImage.Width; i++)
            {
                node.Value = EnergyFunction.GetEnergyAt(InternalImage, 0, i);
                node = node.Next;
            }

            // Calculate all possible seam paths
            EnergyNode firstRowNode = optimalSeams.FirstNode.Bottom;
            for (int i = 1; i < _internalImage.Height; i++)
            {
                node = firstRowNode;
                for (int j = 0; j < _internalImage.Width; j++)
                {
                    Direction d = Direction.TopLeft;
                    double minEnergy = double.MaxValue;
                    if (node.Above.Previous != null && minEnergy > node.Above.Previous.Value)
                    {
                        minEnergy = node.Above.Previous.Value;
                        node.Parent = node.Above.Previous;
                    }
                    if (node.Above != null && minEnergy > node.Above.Value)
                    {
                        minEnergy = node.Above.Value;
                        node.Parent = node.Above;
                    }
                    else if (node.Above.Next != null && minEnergy > node.Above.Next.Value)
                    {
                        minEnergy = node.Above.Next.Value;
                        node.Parent = node.Above.Next;
                    }
                    node.Value = EnergyFunction.GetEnergyAt(InternalImage, i, j) + minEnergy;
                    node = node.Next;
                }
                firstRowNode = firstRowNode.Bottom;
            }

            // Get Minimum Seam
            EnergyNode minInd;
            int minIndex;
            GetMinimumSeam(optimalSeams, out minInd, out minIndex);
            // Construct the final optimal path
            //for (int i = _internalImage.Height - 1; i >= 0; i--)
            //{
            //    Result[i] = minInd;
            //    switch (directions[i, minInd])
            //    {
            //        case Direction.Top:
            //            break;
            //        case Direction.TopLeft:
            //            minInd--;
            //            break;
            //        case Direction.TopRight:
            //            minInd++;
            //            break;
            //    }
            //}
            return optimalSeams.Extract(minInd, minIndex);
        }

        private void GetMinimumSeam(EnergyMap optimalSeams, out EnergyNode minInd, out int minIndex)
        {
            minIndex = 0;
            minInd = optimalSeams.Rows[_internalImage.Height - 1];
            double minTotalSeam = minInd.Value;
            EnergyNode iterator = optimalSeams.Rows[_internalImage.Height - 1];
            for (int i = 0; i < _internalImage.Width; i++)
            {
                if (iterator.Value < minTotalSeam)
                {
                    minTotalSeam = iterator.Value;
                    minInd = iterator;
                    minIndex = i;
                }
                iterator = iterator.Next;
            }
        }
        //private double ReadOptimalSeam(double[,] optimalSeams, int p, int p_2)
        //{
        //    // check if any of the parameters is out of scope
        //    if (p < 0 || p >= _internalImage.Height || p_2 < 0 || p_2 >= _internalImage.Width)
        //        return double.MaxValue;
        //    return optimalSeams[p, p_2];
        //}

        enum Direction
        {
            TopLeft,
            Top,
            TopRight
        }
    }
}
